package com.baitiaojun.log.core.aspect;

import com.alibaba.fastjson.JSON;
import com.baitiaojun.common.enums.impl.CommonStatusEnums;
import com.baitiaojun.common.enums.impl.HttpMethodEnums;
import com.baitiaojun.common.enums.impl.ServerExpEnums;
import com.baitiaojun.common.exception.ServerException;
import com.baitiaojun.common.manager.AsyncManager;
import com.baitiaojun.common.utils.convert.ParamConvertUtils;
import com.baitiaojun.common.utils.exception.ExceptionUtils;
import com.baitiaojun.common.utils.ip.IpUtils;
import com.baitiaojun.common.utils.object.ObjectUtils;
import com.baitiaojun.common.utils.servlet.ServletUtils;
import com.baitiaojun.common.utils.string.StringUtils;
import com.baitiaojun.datasource.core.datasource.DataSourceHolder;
import com.baitiaojun.log.core.annotation.OperLog;
import com.baitiaojun.log.core.dto.SysOperationLogDTO;
import com.baitiaojun.log.core.service.OperationLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.TimerTask;

@Component
@Aspect
public class OperLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(OperLogAspect.class);

    @Autowired
    private OperationLogService operationLogService;

    @Autowired
    private AsyncManager asyncManager;

    /**
     * 方法正常执行完, 结果返回前执行
     * @param operLog
     * @param joinPoint
     * @param result
     */
    @AfterReturning(pointcut = "@annotation(operLog)", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, OperLog operLog, Object result) {
        logger.info("=================方法正常执行完毕, 执行日志处理=================");
        handle(operLog, joinPoint, result, null);
    }

    /**
     * 方法执行异常的时候触发
     * @param operLog
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "@annotation(operLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperLog operLog, Exception e) {
        logger.warn("=================方法执行异常, 执行日志处理=================");
        handle(operLog, joinPoint, null, e);
    }

    /**
     * 日志处理
     * @param operLog
     * @param joinPoint
     */
    public void handle(OperLog operLog, JoinPoint joinPoint, Object result, Exception e) {
        try {
            HttpServletRequest request = ServletUtils.getRequest();
            SysOperationLogDTO sysOperationLogDTO = new SysOperationLogDTO();
            //添加操作模块
            sysOperationLogDTO.setTitle(operLog.title());
            //添加业务类型
            sysOperationLogDTO.setBusinessType(operLog.businessTypeEnums().getCode());
            //添加操作员类型
            sysOperationLogDTO.setOperatorType(operLog.operatorTypeEnums().getCode());
            //添加"类.方法"
            sysOperationLogDTO.setMethod(joinPoint.getTarget().getClass() + "." + joinPoint.getSignature().getName());
            //添加请求方式
            String method = request.getMethod();
            sysOperationLogDTO.setRequestMethod(method);
            //添加操作员
//            sysOperationLogDTO.setOperName(operLog.operator());
            //添加请求地址的目标URI
            sysOperationLogDTO.setOperUri(request.getRequestURI());
            //添加操作人的IP
            String ip = IpUtils.getIp(request);
            sysOperationLogDTO.setOperIp(ip);
            //添加操作地点
            sysOperationLogDTO.setOperLocation(IpUtils.getAddressInfo(ip));
            //添加请求参数
            String params = getParam(joinPoint, method, request);
            sysOperationLogDTO.setOperParam(params);
            //添加返回值
            if (ObjectUtils.isNotNull(result)) {
                sysOperationLogDTO.setJsonResult(JSON.toJSONString(result));
            }
            //添加状态
            if (ObjectUtils.isNotNull(result)) {
                sysOperationLogDTO.setStatus(CommonStatusEnums.SUCCESS.getCode());
            }
            if (ObjectUtils.isNotNull(e)) {
                sysOperationLogDTO.setStatus(CommonStatusEnums.FAIL.getCode());
                //添加错误信息
                sysOperationLogDTO.setErrorMsg(e.getMessage());
            }
            //tenant不为空就切换该租户数据源
            String tenant = operLog.tenant();
            if (StringUtils.isNotEmpty(tenant)) {
                DataSourceHolder.setDb(tenant);
            }
            asyncManager.execute(new TimerTask() {
                @Override
                public void run() {
                    logger.info("=================调用[SysLogServiceImpl.addLog]进行操作日志落库=================");
                    operationLogService.addLog(sysOperationLogDTO);
                }
            });
        } catch (Exception exception) {
            logger.warn(ExceptionUtils.getExpMsg(exception));
            throw new ServerException(ServerExpEnums.EXP_INVOKE_LOG_SERVICE);
        }
    }

    private String getParam(JoinPoint joinPoint, String method, HttpServletRequest request) {
        String params;
        if (HttpMethodEnums.POST.getName().equals(method) || HttpMethodEnums.PUT.getName().equals(method)) {
            //获取json参数
            Object[] args = joinPoint.getArgs();
            params = ParamConvertUtils.convertJsonStr(args);
        } else {
            //获取地址栏参数
            Map<?, ?> attributeMaps = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            if (attributeMaps != null) {
                params = attributeMaps.toString();
            } else {
                StringBuilder builder = new StringBuilder();
                Object[] args = joinPoint.getArgs();
                for (int i = 0; i < args.length; i++) {
                    builder.append(args[i]);
                    if (args.length != i + 1) {
                        builder.append(":");
                    }
                }
                params = builder.toString();
            }
        }
        return params;
    }
}
